Open Distro for Elasticsearchの異常検出(Anomaly Detection)をDockerを使ってローカルで試してみた
CX事業本部のうらわです。
Amazon Elasticsearch Serviceではバージョン7.4から異常検出(Anomaly Detection)の機能を使うことができるようになりました。
面白そうな機能なので実際にAWSでAmazon Elasticsearch Serviceを起動してデータを入れて検証…とやっているとドメイン構築やアクセスポリシーの設定等が必要で少し手間がかかるため、Dockerを使ってローカルマシンで検証してみました。
Open Distro for Elasticsearchについて
Amazon Elasticsearch Serviceの異常検出機能はAWSが公開・管理しているOpen Distro for Elasticsearchによって動作しています。
そのため、今回はElastic社が提供しているElasticsearchのDockerイメージではなく、Amazon Elasticsearch Serviceと同じ異常検出のプラグインがインストールされているOpen Distro for Elasticsearchのイメージを利用します。
Elastic社が開発しているElasticsearchとOpen Distro Elasticsearchの違いについては以下記事をご参照ください。
異常検出機能自体は以下の記事に解説がありますので本記事では割愛します。
環境構築
検証は以下の環境で進めます。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.6 BuildVersion: 19G2021 $ node -v v12.18.3 $ npm -v 6.14.6 $ docker --version Docker version 19.03.13, build 4484c46d9d $ docker-compose --version docker-compose version 1.27.4, build 40524192
本記事で紹介しているコードは以下のGitHubリポジトリに格納してあります。
https://github.com/urawa72/odfe-sample
Elasticsearch/KibanaのDockerによる環境構築はOpen Distro Elasticsearch公式ドキュメントのdocker-compose.yml
のサンプルをそのまま使用すると簡単にできます。
https://opendistro.github.io/for-elasticsearch-docs/docs/install/docker/#sample-docker-compose-file
ただし、今回はより手軽に検証するため、デフォルトで有効になっているSSLやログイン等のセキュリティ系のプラグインを無効にします。
この方法についても公式ドキュメントに手順が記載されています。
https://opendistro.github.io/for-elasticsearch-docs/docs/security/configuration/disable/
まずはセキュリティ系プラグインを削除したDockerイメージを作成します。この際、以下のkibana.yml
を使用します。
--- server.name: kibana server.host: "0" elasticsearch.hosts: http://localhost:9200
FROM amazon/opendistro-for-elasticsearch-kibana:1.11.0 RUN /usr/share/kibana/bin/kibana-plugin remove opendistro_security COPY --chown=kibana:kibana kibana.yml /usr/share/kibana/config/
ビルドしておきます。
docker build -t kibana-no-security .
イメージがビルドできたら、サンプルのdocker-compose.yml
を修正します。以下のハイライト箇所が公式ドキュメントからの変更点です。
version: '3' services: odfe-node1: image: amazon/opendistro-for-elasticsearch:1.11.0 container_name: odfe-node1 environment: - cluster.name=odfe-cluster - node.name=odfe-node1 - discovery.seed_hosts=odfe-node1,odfe-node2 - cluster.initial_master_nodes=odfe-node1,odfe-node2 - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM - opendistro_security.disabled=true # セキュリティプラグインの無効化 ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65536 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems hard: 65536 volumes: - odfe-data1:/usr/share/elasticsearch/data ports: - 9200:9200 - 9600:9600 # required for Performance Analyzer networks: - odfe-net odfe-node2: image: amazon/opendistro-for-elasticsearch:1.11.0 container_name: odfe-node2 environment: - cluster.name=odfe-cluster - node.name=odfe-node2 - discovery.seed_hosts=odfe-node1,odfe-node2 - cluster.initial_master_nodes=odfe-node1,odfe-node2 - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - opendistro_security.disabled=true # セキュリティプラグインの無効化 ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65536 hard: 65536 volumes: - odfe-data2:/usr/share/elasticsearch/data networks: - odfe-net kibana: image: kibana-no-security # セキュリティプラグインを削除したイメージを指定 container_name: odfe-kibana ports: - 5601:5601 expose: - "5601" environment: ELASTICSEARCH_URL: http://odfe-node1:9200 # https を http に ELASTICSEARCH_HOSTS: http://odfe-node1:9200 # https を http に networks: - odfe-net volumes: odfe-data1: odfe-data2: networks: odfe-net:
起動確認
docker-compose
でコンテナを起動します。
docker-compose up -d
Elasticsearchが起動しているかどうかはAPIで確認することができます。
$ curl -X GET http://localhost:9200/ { "name" : "odfe-node1", "cluster_name" : "odfe-cluster", "cluster_uuid" : "SrK7wcBZRRW1fMHyS0lDkQ", "version" : { "number" : "7.9.1", "build_flavor" : "oss", "build_type" : "tar", "build_hash" : "083627f112ba94dffc1232e8b42b73492789ef91", "build_date" : "2020-09-01T21:22:21.964974Z", "build_snapshot" : false, "lucene_version" : "8.6.2", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search" }
Kibanaはブラウザでhttp://localhost:5601
にアクセスして確認します。ちなみに、セキュリティ系のプラグインが有効のままの場合は最初にログイン画面が表示されます。
データ投入
今回はBulk用のファイルの生成とElasticsearchへのデータ投入用のスクリプトをTypeScriptで作成しました。
データのフォーマットは以下のような適当な時系列データとします。
value
は20〜30のランダム値(小数点第三位まで)です。この値の範囲から大きく外れた値が投入されたら異常とする、というのを期待します。
{ sequence: 1, value: 24.245, timestamp: "2020-11-17T03:18:32.159+09:00" }
APIリファレンスのコードを参考にして5時間分(18000秒)のndjsonファイルを作成します。スクリプト実行時の5時間前からのデータを作成します。
https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-helpers.html
import fs from 'fs'; import { DateTime } from 'luxon'; const output = fs.createWriteStream('output.ndjson', 'utf8'); let date = DateTime.local().minus({ hours: 5 }); for (let i = 1; i <= 18000; i++) { date = date.plus(1000); const param = { sequence: i, value: Math.round((Math.random() * (35 - 20) + 20) * 10000) / 10000, timestamp: date.toISO(), }; output.write(JSON.stringify(param) + '\n'); }
作成したndjsonファイルをインプットとしてElasticsearchに一括インデックスします。
import * as Es from '@elastic/elasticsearch'; import { createReadStream } from 'fs'; import split from 'split2'; const client = new Es.Client({ node: 'http://localhost:9200', }); const bulkIndex = async () => { const result = await client.helpers.bulk({ datasource: createReadStream('output.ndjson').pipe(split()), onDocument() { return { index: { _index: 'my-test' }, }; }, }); console.log(result); }; bulkIndex().catch(console.log);
上記の二つのスクリプトを実行し、インデックスの状態を確認します。docs.countが18000になっていれば成功です。
yarn ts-node make-ndjson.ts yarn ts-node bulk-index.ts curl -X GET "http://localhost:9200/_cat/indices/my-test?v&pretty" health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open my-test JDnu70MSTsOglW_EkXj7gQ 1 1 18000 0 1.8mb 899.3kb
Kibanaでdetectorを作成
Kibanaの左サイドバーメニューからAnomaly Detectorを選択し、新しいdetectorを作成します。
今回は以下のような設定としました。
大項目 | 中項目 | 値 |
---|---|---|
Name and description | Name | my-test-detector |
Data Source | Index | my-test |
Data Source | Timestamp field | timestamp |
Detector Operation Setting | Detector interval | 1 |
Detector Operation Setting | Window delay | 1 |
続いてmodelを作成します。下記画像のように設定しました。
そのままdetectorをスタートさせます。
detectorの初期化処理が始まります。
異常値を投入してみる
1秒ごとにElasticsearchにデータをインデックスするスクリプトを実行します。100回に1回、異常値としてvalue
が100のデータを投入してみます。
import * as Es from '@elastic/elasticsearch'; import { DateTime } from 'luxon'; const client = new Es.Client({ node: 'http://localhost:9200', }); const createDoc = async () => { const sleep = () => new Promise((resolve) => setTimeout(resolve, 1000)); const loop = true; let idx = 0; while (loop) { ++idx; let value = Math.round((Math.random() * (35 - 20) + 20) * 10000) / 10000; if (idx % 100 === 0) value = 100; const param = { sequence: idx, value: value, timestamp: DateTime.local().toISO(), }; console.log('Create Doc:', JSON.stringify(param)); await client.index({ index: 'my-test', body: param, }); await sleep(); } }; createDoc().catch(console.log);
スクリプトを起動して放置します。
yarn ts-node create-doc.ts
少し経つとdetectorの画面に異常検出の結果が表示されます。一応こちらの意図通りに動作しているように見えます。
なお、Anomaly grade
とData confidence
についてはOpen Distro for Elasticsearchの公式ドキュメントに記載があります。
Anomaly grade is a number between 0 and 1 that indicates the level of severity of how anomalous a data point is. An anomaly grade of 0 represents “not an anomaly,” and a non-zero value represents the relative severity of the anomaly.The confidence score is an estimate of the probability that the reported anomaly grade matches the expected anomaly grade. Confidence increases as the model observes more data and learns the data behavior and trends. Note that confidence is distinct from model accuracy.
まとめ
設定値の違いなど細かい仕様まで理解しきれていませんが、ひとまずローカルで異常検出機能を試すことはできました。
Amazon Elasticsearch Serviceでドメインを作成してAnomaly Detectorを使う前に、ローカルでDockerを使ってテストしてみるのは有効だと思います。
今回は都合の良い自作の時系列データを使いましたが、次回は生データを使った際にどのように動作するのか試してみたいと思います。また、この異常検出の結果をAlert機能でSlack等に通知もできる(これもOpen Distro for Elasticsearchの機能)ので、こちらも試してみたいと思います。